#ifndef JASS_TYPE_H
#define JASS_TYPE_H

#include <string>
#include <any>
#include <memory>

namespace Ast
{
    namespace Visitor
    {

        class JassTypeVisitor;
        class PrimitiveJassType;
        class JassValue;
        class HandleJassValue;

        class JassType : public std::enable_shared_from_this<JassType>
        {
        public:
            virtual ~JassType() = default;
            
            virtual std::any visit(std::shared_ptr<JassTypeVisitor> visitor) {}

            virtual const std::string getName() { return""; }

            virtual bool isAssignableFrom(std::shared_ptr<JassType> value) const { return false; }

            virtual bool isNullable() const { return true; }

            virtual std::shared_ptr<JassValue> getNullValue() { return nullptr; }

            static std::shared_ptr<PrimitiveJassType> INTEGER();
            static std::shared_ptr<PrimitiveJassType> STRING();
            static std::shared_ptr<PrimitiveJassType> CODE();
            static std::shared_ptr<PrimitiveJassType> REAL();
            static std::shared_ptr<PrimitiveJassType> BOOLEAN();
            static std::shared_ptr<PrimitiveJassType> NOTHING();
            static std::shared_ptr<PrimitiveJassType> DUMMY();
        };

        class PrimitiveJassType : public JassType
        {
        private:
            std::string name_;
            std::shared_ptr<JassValue> nullValue_;

        public:
            PrimitiveJassType(const std::string name, std::shared_ptr<JassValue> nullValue) :
                name_(name), nullValue_(nullValue) {}

            virtual const std::string getName() const { return name_; }

            virtual bool isAssignableFrom(std::shared_ptr<JassType> value) const;

            virtual bool isNullable() const { return false; }

            virtual std::any visit(std::shared_ptr<JassTypeVisitor> visitor);

            virtual std::shared_ptr<JassValue> getNullValue() { return nullValue_; }
        };

        class StringJassType : public PrimitiveJassType
        {
        public:
            StringJassType(const std::string name);

            virtual bool isNullable() const { return true; }
        };

        class CodeJassType : public PrimitiveJassType
        {
        public:
            CodeJassType(const std::string name);

            virtual bool isNullable() const { return true; }
        };

        class RealJassType : public PrimitiveJassType
        {
        public:
            RealJassType(std::string name, std::shared_ptr<JassValue> nullValue);

            virtual bool isAssignableFrom(std::shared_ptr<JassType> value) const;
        };

        class ArrayJassType : public JassType
        {
        private:
            std::shared_ptr<JassType> primitiveType_;

        public:
            ArrayJassType(std::shared_ptr<JassType> primitiveType)
            {
                this->primitiveType_ = primitiveType;
            }

            virtual const std::string getName() const
            {
                return this->primitiveType_->getName() + " array";
            }

            virtual bool isAssignableFrom(std::shared_ptr<JassType> value) const
            {
                return value.get() == this;
            }

            std::shared_ptr<JassType> getPrimitiveType() const { return primitiveType_; }

            virtual std::any visit(std::shared_ptr<JassTypeVisitor> visitor);

            virtual bool isNullable() const { return false; }

            virtual std::any getNullValue() const { return nullptr; }

        };

        class HandleJassType : public JassType
        {
        private:
            std::shared_ptr<HandleJassType> superType_;
            std::string name_;

        public:
            HandleJassType(std::shared_ptr<HandleJassType> superType, const std::string& name)
                : superType_(superType), name_(name) {}

            virtual bool isAssignableFrom(std::shared_ptr<JassType> value) const;

            virtual const std::string getName() const { return name_; }

            std::shared_ptr<HandleJassType> getSuperType()
            {
                return superType_;
            }

            void setSuperType(std::shared_ptr<HandleJassType> superType)
            {
                superType_ = superType;
            }

            bool isNullable()
            {
                return true;
            }

            std::shared_ptr<HandleJassValue> getNullValue() const;
        };

    }
}

#endif  // JASS_TYPE_H
